#include "vsf.h"
#include <libusb.h>
#include <pthread.h>
#include <poll.h>

struct vsflinux_libusb_cb_t
{
	libusb_hotplug_callback_handle handle;
	libusb_context *ctx;
	libusb_hotplug_event events;
	libusb_hotplug_flag flags;
	int vendor_id;
	int product_id;
	int dev_class;
	libusb_hotplug_callback_fn cb_fn;
	void *user_data;
	struct vsflist_t list;
};

struct vsflinux_libusb_t
{
	struct vsflist_t cblist;
	libusb_hotplug_callback_handle cbhandle;
	struct vsflist_t devlist;
	struct vsflist_t translist;
	struct vsfsm_thread_t *thread;
	struct vsfusbh_libusb_dev_t *curdev;

	int fd;
	struct libusb_pollfd pollfd[1];
} static vsflinux_libusb;

struct libusb_transfer_ext
{
	struct libusb_transfer transfer;
	struct vsfhcd_urb_t *urb;
	struct vsfsm_t sm;
	struct vsflist_t list;
};

static void libusb_on_event(void *param, struct vsfusbh_libusb_dev_t *dev, enum vsfusbh_libusb_evt_t evt)
{
	vsflist_append(&vsflinux_libusb.devlist, &dev->usrlist);
	vsfsm_post_evt_pending(&vsflinux_libusb.thread->sm, VSFSM_EVT_USER + evt);
}

static void *libusb_thread(void *param)
{
	uint8_t origlevel;
	vsfsm_evt_t evt;

	while (1)
	{
		evt = vsfsm_thread_wait();
		if (evt == VSFSM_EVT_URB_COMPLETE)
		{
			struct libusb_transfer_ext *transfer_ext;
			origlevel = vsfsm_sched_lock();

			transfer_ext = vsflist_get_container(vsflinux_libusb.translist.next, struct libusb_transfer_ext, list);
			vsflinux_libusb.translist.next = vsflinux_libusb.translist.next->next;
			vsfsm_sched_unlock(origlevel);

			if (transfer_ext->transfer.callback != NULL)
				transfer_ext->transfer.callback(&transfer_ext->transfer);

			for (int i = 0; i < dimof(vsflinux_libusb.pollfd); i++)
				if (vsflinux_libusb.pollfd[i].fd >= 0)
					vsflinux_fd_rx_sendevt(vsflinux_fd_get(vsflinux_libusb.pollfd[i].fd));
		}
		else if ((evt == (VSFSM_EVT_USER + VSFUSBH_LIBUSB_EVT_ON_ARRIVED)) ||
			(evt == (VSFSM_EVT_USER + VSFUSBH_LIBUSB_EVT_ON_LEFT)))
		{
			struct vsfusbh_libusb_dev_t *dev;
			evt -= VSFSM_EVT_USER;
			origlevel = vsfsm_sched_lock();
			dev = vsflist_get_container(vsflinux_libusb.devlist.next, struct vsfusbh_libusb_dev_t, usrlist);
			vsflinux_libusb.devlist.next = vsflinux_libusb.devlist.next->next;
			vsfsm_sched_unlock(origlevel);

			vsflist_foreach(cb, vsflinux_libusb.cblist.next, struct vsflinux_libusb_cb_t, list)
			{
				if (((cb->vendor_id == LIBUSB_HOTPLUG_MATCH_ANY) || (cb->vendor_id == dev->dev->device_desc->idVendor)) &&
					((cb->product_id == LIBUSB_HOTPLUG_MATCH_ANY) || (cb->product_id == dev->dev->device_desc->idProduct)) &&
					((cb->dev_class == LIBUSB_HOTPLUG_MATCH_ANY) || (cb->dev_class == dev->dev->device_desc->bDeviceClass)) &&
					(cb->cb_fn != NULL))
				{
					switch (evt)
					{
					case VSFUSBH_LIBUSB_EVT_ON_ARRIVED:
						if (cb->events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
							cb->cb_fn(cb->ctx, (libusb_device *)dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, cb->user_data);
						break;
					case VSFUSBH_LIBUSB_EVT_ON_LEFT:
						if (cb->events & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
							cb->cb_fn(cb->ctx, (libusb_device *)dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, cb->user_data);
						break;
					}
				}
			}
		}
	}
}

int libusb_init(libusb_context **context)
{
	pthread_t pthread;

	vsflinux_libusb.cblist.next = NULL;
	vsflinux_libusb.cbhandle = 1;
	vsfusbh_libusb_set_evthandler(NULL, libusb_on_event);

	pthread_create(&pthread, NULL, libusb_thread, NULL);
	vsflinux_libusb.thread = vsflinux_thread_get(pthread);

	vsflinux_libusb.fd = vsflinux_fd_new(NULL, 0, NULL);
	vsflinux_libusb.pollfd[0].fd = vsflinux_libusb.fd;
	vsflinux_libusb.pollfd[0].events = POLLIN;
	return LIBUSB_SUCCESS;
}

void libusb_exit(struct libusb_context *ctx)
{
	if (vsflinux_libusb.fd >= 0)
		vsflinux_fd_del(vsflinux_fd_get(vsflinux_libusb.fd));
}

int libusb_get_next_timeout(libusb_context *ctx, struct timeval *tv)
{
	return 0;
}

int libusb_has_capability(uint32_t capability)
{
	switch (capability)
	{
	case LIBUSB_CAP_HAS_CAPABILITY:
	case LIBUSB_CAP_HAS_HOTPLUG:
		return 1;
	default:
		return 0;
	}
}

int libusb_hotplug_register_callback(libusb_context *ctx,
		libusb_hotplug_event events,
		libusb_hotplug_flag flags,
		int vendor_id,
		int product_id,
		int dev_class,
		libusb_hotplug_callback_fn cb_fn,
		void *user_data,
		libusb_hotplug_callback_handle *callback_handle)
{
	struct vsflinux_libusb_cb_t *cb = malloc(sizeof(struct vsflinux_libusb_cb_t));

	if (!cb) return LIBUSB_ERROR_NO_MEM;

	cb->handle = vsflinux_libusb.cbhandle++;
	if (callback_handle != NULL)
		*callback_handle = cb->handle;
	cb->ctx = ctx;
	cb->events = events;
	cb->flags = flags;
	cb->vendor_id = vendor_id;
	cb->product_id = product_id;
	cb->dev_class = dev_class;
	cb->cb_fn = cb_fn;
	cb->user_data = user_data;
	cb->list.next = vsflinux_libusb.cblist.next;
	vsflinux_libusb.cblist.next = &cb->list;
	return LIBUSB_SUCCESS;
}

void libusb_hotplug_deregister_callback(libusb_context *ctx,
		libusb_hotplug_callback_handle callback_handle)
{
	vsflist_foreach(cb, vsflinux_libusb.cblist.next, struct vsflinux_libusb_cb_t, list)
	{
		if (cb->handle == callback_handle)
		{
			vsflist_remove(&vsflinux_libusb.cblist.next, &cb->list);
			free(cb);
			break;
		}
	}
}

ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device *** list)
{
	ssize_t devnum = vsfusbh_libusb_enum_begin();

	*list = NULL;
	if ((devnum > 0) && (list != NULL))
	{
		ssize_t i;
		libusb_device **devlist = (libusb_device **)malloc((devnum + 1) * sizeof(libusb_device *));

		if (!devlist) return 0;
		for (i = 0; i < devnum; i++)
		{
			devlist[i] = (libusb_device *)vsfusbh_libusb_enum_next();
			vsfusbh_libusb_ref((struct vsfusbh_libusb_dev_t *)devlist[i]);
		}
		devlist[i] = NULL;
		*list = devlist;
	}
	vsfusbh_libusb_enum_end();
	return devnum;
}

void libusb_free_device_list(libusb_device **list, int unref_devices)
{
	if (list != NULL)
	{
		if (unref_devices)
		{
			while (*list != NULL)
				vsfusbh_libusb_deref((struct vsfusbh_libusb_dev_t *)(*list++));
		}
		free(list);
	}
}

int libusb_open(libusb_device *dev, libusb_device_handle **dev_handle)
{
	if (!vsfusbh_libusb_open((struct vsfusbh_libusb_dev_t *)dev))
	{
		if (dev_handle != NULL)
			*dev_handle = (libusb_device_handle *)dev;
		return LIBUSB_SUCCESS;
	}
	return LIBUSB_ERROR_BUSY;
}

void libusb_close(libusb_device_handle *dev_handle)
{
	vsfusbh_libusb_close((struct vsfusbh_libusb_dev_t *)dev_handle);
}

int libusb_detach_kernel_driver(libusb_device_handle *dev_handle, int interface_number)
{
	return LIBUSB_ERROR_NOT_SUPPORTED;
}

int libusb_kernel_driver_active(libusb_device_handle *dev_handle, int interface_number)
{
	return LIBUSB_ERROR_NOT_SUPPORTED;
}

uint8_t libusb_get_bus_number(libusb_device *dev)
{
	return 0;
}

uint8_t libusb_get_device_address(libusb_device *dev)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev;
	return ldev->dev->hcddev.devnum;
}

int libusb_get_max_packet_size(libusb_device *dev, unsigned char endpoint)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev;
	unsigned char epaddr = endpoint & 0x7F;
	return ((endpoint & USB_DIR_MASK) == USB_DIR_IN) ?
		ldev->dev->ep_mps_in[epaddr] : ldev->dev->ep_mps_out[epaddr];
}

int libusb_get_device_speed(libusb_device *dev)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev;
	switch (ldev->dev->hcddev.speed)
	{
	case USB_SPEED_LOW:		return LIBUSB_SPEED_LOW;
	case USB_SPEED_FULL:	return LIBUSB_SPEED_FULL;
	case USB_SPEED_HIGH:	return LIBUSB_SPEED_HIGH;
	case USB_SPEED_SUPER:	return LIBUSB_SPEED_SUPER;
	default:				return LIBUSB_SPEED_UNKNOWN;
	}
}

static int libusb_submit_urb(struct vsfusbh_libusb_dev_t *ldev)
{
	struct vsfsm_thread_t *thread = vsfsm_thread_get_cur();
	struct vsfhcd_urb_t *urb = ldev->urb;
	uint8_t origlevel;
	int err = LIBUSB_SUCCESS;

	urb->notifier_sm = &thread->sm;
	origlevel = vsfsm_sched_lock();
	if (vsfusbh_submit_urb(ldev->usbh, urb))
		err = LIBUSB_ERROR_IO;
	vsfsm_sched_unlock(origlevel);
	if (err) return err;
	vsfsm_thread_wfe(VSFSM_EVT_URB_COMPLETE);
	return urb->status != URB_OK ? LIBUSB_ERROR_IO : LIBUSB_SUCCESS;
}

int libusb_control_transfer(libusb_device_handle *dev_handle,
	uint8_t bRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
	unsigned char *data, uint16_t wLength, unsigned int timeout)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev_handle;
	struct vsfhcd_device_t *hcddev = &ldev->dev->hcddev;
	struct vsfhcd_urb_t *urb = ldev->urb;
	int err;

	urb->pipe = ((bRequestType & USB_DIR_MASK) == USB_DIR_IN) ?
		usb_rcvctrlpipe(hcddev, 0) : usb_sndctrlpipe(hcddev, 0);
	urb->setup_packet.bRequestType = bRequestType;
	urb->setup_packet.bRequest = bRequest;
	urb->setup_packet.wValue = wValue;
	urb->setup_packet.wIndex = wIndex;
	urb->setup_packet.wLength = wLength;
	urb->transfer_buffer = data;
	urb->transfer_length = wLength;
	urb->timeout = timeout;
	err = libusb_submit_urb(ldev);
	if (!err) err = urb->actual_length;
	return err;
}

int libusb_bulk_transfer(libusb_device_handle *dev_handle,
	unsigned char endpoint, unsigned char *data, int length,
	int *actual_length, unsigned int timeout)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev_handle;
	struct vsfhcd_device_t *hcddev = &ldev->dev->hcddev;
	struct vsfhcd_urb_t *urb = ldev->urb;
	unsigned char epaddr = endpoint & 0x7F;
	int ret;

	urb->pipe = ((endpoint & USB_DIR_MASK) == USB_DIR_IN) ?
		usb_rcvbulkpipe(hcddev, epaddr) : usb_sndbulkpipe(hcddev, epaddr);
	urb->transfer_buffer = data;
	urb->transfer_length = length;
	urb->timeout = timeout;

	ret = libusb_submit_urb(ldev);
	if (!ret && (actual_length != NULL))
		*actual_length = urb->actual_length;
	return ret;
}

int libusb_interrupt_transfer(libusb_device_handle *dev_handle,
	unsigned char endpoint, unsigned char *data, int length,
	int *actual_length, unsigned int timeout)
{
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)dev_handle;
	struct vsfhcd_device_t *hcddev = &ldev->dev->hcddev;
	struct vsfhcd_urb_t *urb = ldev->urb;
	unsigned char epaddr = endpoint & 0x7F;
	int ret;

	urb->pipe = ((endpoint & USB_DIR_MASK) == USB_DIR_IN) ?
		usb_rcvintpipe(hcddev, epaddr) : usb_sndintpipe(hcddev, epaddr);
	urb->transfer_buffer = data;
	urb->transfer_length = length;
	urb->timeout = timeout;

	ret = libusb_submit_urb(ldev);
	if (!ret && (actual_length != NULL))
		*actual_length = urb->actual_length;
	return ret;
}

int libusb_get_device_descriptor(libusb_device *dev, struct libusb_device_descriptor *desc)
{
	int err;
	err = libusb_get_descriptor((libusb_device_handle *)dev, USB_DT_DEVICE, 0,
			(unsigned char *)desc, sizeof(struct libusb_device_descriptor));
	return err < 0 ? err : LIBUSB_SUCCESS;
}

int libusb_set_configuration(libusb_device_handle *dev_handle, int configuration)
{
	struct libusb_config_descriptor *config;
	int err;
	err = libusb_control_transfer(dev_handle, USB_DIR_OUT,
			USB_REQ_SET_CONFIGURATION, configuration, 0, NULL, 0, 1000);
	if (err < 0) return err;

	err = libusb_get_active_config_descriptor((libusb_device *)dev_handle, &config);
	if (err < 0) return err;

	libusb_free_config_descriptor(config);
	return err;
}

int libusb_get_configuration(libusb_device_handle *dev_handle, int *config)
{
	int err;
	*config = 0;
	err = libusb_control_transfer(dev_handle, USB_DIR_IN,
			USB_REQ_GET_CONFIGURATION, 0, 0, (unsigned char *)config, 1, 1000);
	return err < 0 ? err : LIBUSB_SUCCESS;
}

// desc parser from original libusb
#define DESC_HEADER_LENGTH			2
#define DEVICE_DESC_LENGTH			18
#define CONFIG_DESC_LENGTH			9
#define INTERFACE_DESC_LENGTH		9
#define ENDPOINT_DESC_LENGTH		7
#define ENDPOINT_AUDIO_DESC_LENGTH	9

#define USB_MAXENDPOINTS			32
#define USB_MAXINTERFACES			32
#define USB_MAXCONFIG				8

#define usbi_err(ctx, fmt, ...)		//vsfdbg_printf("usbi_err: " fmt VSFCFG_DEBUG_LINEEND, ##__VA_ARGS__)
#define usbi_warn(ctx, fmt, ...)	//vsfdbg_printf("usbi_warn: " fmt VSFCFG_DEBUG_LINEEND, ##__VA_ARGS__)
#define usbi_dbg(ctx, fmt, ...)		//vsfdbg_printf("usbi_dbg: " fmt VSFCFG_DEBUG_LINEEND, ##__VA_ARGS__)

struct usb_descriptor_header {
	uint8_t bLength;
	uint8_t bDescriptorType;
};

static inline void *usbi_reallocf(void *ptr, size_t size)
{
	void *ret = realloc(ptr, size);
	if (!ret)
		free(ptr);
	return ret;
}

static int usbi_parse_descriptor(const unsigned char *source, const char *descriptor,
	void *dest, int host_endian)
{
	const unsigned char *sp = source;
	unsigned char *dp = dest;
	uint16_t w;
	const char *cp;
	uint32_t d;

	for (cp = descriptor; *cp; cp++) {
		switch (*cp) {
			case 'b':	/* 8-bit byte */
				*dp++ = *sp++;
				break;
			case 'w':	/* 16-bit word, convert from little endian to CPU */
				dp += ((uintptr_t)dp & 1);	/* Align to word boundary */

				if (host_endian) {
					memcpy(dp, sp, 2);
				} else {
					w = (sp[1] << 8) | sp[0];
					*((uint16_t *)dp) = w;
				}
				sp += 2;
				dp += 2;
				break;
			case 'd':	/* 32-bit word, convert from little endian to CPU */
				dp += ((uintptr_t)dp & 1);	/* Align to word boundary */

				if (host_endian) {
					memcpy(dp, sp, 4);
				} else {
					d = (sp[3] << 24) | (sp[2] << 16) |
						(sp[1] << 8) | sp[0];
					*((uint32_t *)dp) = d;
				}
				sp += 4;
				dp += 4;
				break;
			case 'u':	/* 16 byte UUID */
				memcpy(dp, sp, 16);
				sp += 16;
				dp += 16;
				break;
		}
	}

	return (int) (sp - source);
}

static void clear_endpoint(struct libusb_endpoint_descriptor *endpoint)
{
	free((void *) endpoint->extra);
}

static int parse_endpoint(struct libusb_context *ctx,
	struct libusb_endpoint_descriptor *endpoint, unsigned char *buffer,
	int size, int host_endian)
{
	struct usb_descriptor_header header;
	unsigned char *extra;
	unsigned char *begin;
	int parsed = 0;
	int len;

	if (size < DESC_HEADER_LENGTH) {
		usbi_err(ctx, "short endpoint descriptor read %d/%d",
			 size, DESC_HEADER_LENGTH);
		return LIBUSB_ERROR_IO;
	}

	usbi_parse_descriptor(buffer, "bb", &header, 0);
	if (header.bDescriptorType != LIBUSB_DT_ENDPOINT) {
		usbi_err(ctx, "unexpected descriptor %x (expected %x)",
			header.bDescriptorType, LIBUSB_DT_ENDPOINT);
		return parsed;
	}
	if (header.bLength > size) {
		usbi_warn(ctx, "short endpoint descriptor read %d/%d",
			  size, header.bLength);
		return parsed;
	}
	if (header.bLength >= ENDPOINT_AUDIO_DESC_LENGTH)
		usbi_parse_descriptor(buffer, "bbbbwbbb", endpoint, host_endian);
	else if (header.bLength >= ENDPOINT_DESC_LENGTH)
		usbi_parse_descriptor(buffer, "bbbbwb", endpoint, host_endian);
	else {
		usbi_err(ctx, "invalid endpoint bLength (%d)", header.bLength);
		return LIBUSB_ERROR_IO;
	}

	if ((endpoint->bEndpointAddress & USB_DIR_MASK) == USB_DIR_IN)
		vsflinux_libusb.curdev->dev->ep_mps_in[endpoint->bEndpointAddress & 0x7F] = endpoint->wMaxPacketSize;
	else
		vsflinux_libusb.curdev->dev->ep_mps_out[endpoint->bEndpointAddress & 0x7F] = endpoint->wMaxPacketSize;

	buffer += header.bLength;
	size -= header.bLength;
	parsed += header.bLength;

	/* Skip over the rest of the Class Specific or Vendor Specific */
	/*  descriptors */
	begin = buffer;
	while (size >= DESC_HEADER_LENGTH) {
		usbi_parse_descriptor(buffer, "bb", &header, 0);
		if (header.bLength < DESC_HEADER_LENGTH) {
			usbi_err(ctx, "invalid extra ep desc len (%d)",
				 header.bLength);
			return LIBUSB_ERROR_IO;
		} else if (header.bLength > size) {
			usbi_warn(ctx, "short extra ep desc read %d/%d",
				  size, header.bLength);
			return parsed;
		}

		/* If we find another "proper" descriptor then we're done  */
		if ((header.bDescriptorType == LIBUSB_DT_ENDPOINT) ||
				(header.bDescriptorType == LIBUSB_DT_INTERFACE) ||
				(header.bDescriptorType == LIBUSB_DT_CONFIG) ||
				(header.bDescriptorType == LIBUSB_DT_DEVICE))
			break;

		usbi_dbg("skipping descriptor %x", header.bDescriptorType);
		buffer += header.bLength;
		size -= header.bLength;
		parsed += header.bLength;
	}

	/* Copy any unknown descriptors into a storage area for drivers */
	/*  to later parse */
	len = (int)(buffer - begin);
	if (!len) {
		endpoint->extra = NULL;
		endpoint->extra_length = 0;
		return parsed;
	}

	extra = malloc(len);
	endpoint->extra = extra;
	if (!extra) {
		endpoint->extra_length = 0;
		return LIBUSB_ERROR_NO_MEM;
	}

	memcpy(extra, begin, len);
	endpoint->extra_length = len;

	return parsed;
}

static void clear_interface(struct libusb_interface *usb_interface)
{
	int i;
	int j;

	if (usb_interface->altsetting) {
		for (i = 0; i < usb_interface->num_altsetting; i++) {
			struct libusb_interface_descriptor *ifp =
				(struct libusb_interface_descriptor *)
				usb_interface->altsetting + i;
			free((void *) ifp->extra);
			if (ifp->endpoint) {
				for (j = 0; j < ifp->bNumEndpoints; j++)
					clear_endpoint((struct libusb_endpoint_descriptor *)
						       ifp->endpoint + j);
			}
			free((void *) ifp->endpoint);
		}
	}
	free((void *) usb_interface->altsetting);
	usb_interface->altsetting = NULL;
}

static int parse_interface(libusb_context *ctx,
	struct libusb_interface *usb_interface, unsigned char *buffer, int size,
	int host_endian)
{
	int i;
	int len;
	int r;
	int parsed = 0;
	int interface_number = -1;
	struct usb_descriptor_header header;
	struct libusb_interface_descriptor *ifp;
	unsigned char *begin;

	usb_interface->num_altsetting = 0;

	while (size >= INTERFACE_DESC_LENGTH) {
		struct libusb_interface_descriptor *altsetting =
			(struct libusb_interface_descriptor *) usb_interface->altsetting;
		altsetting = usbi_reallocf(altsetting,
			sizeof(struct libusb_interface_descriptor) *
			(usb_interface->num_altsetting + 1));
		if (!altsetting) {
			r = LIBUSB_ERROR_NO_MEM;
			goto err;
		}
		usb_interface->altsetting = altsetting;

		ifp = altsetting + usb_interface->num_altsetting;
		usbi_parse_descriptor(buffer, "bbbbbbbbb", ifp, 0);
		if (ifp->bDescriptorType != LIBUSB_DT_INTERFACE) {
			usbi_err(ctx, "unexpected descriptor %x (expected %x)",
				 ifp->bDescriptorType, LIBUSB_DT_INTERFACE);
			return parsed;
		}
		if (ifp->bLength < INTERFACE_DESC_LENGTH) {
			usbi_err(ctx, "invalid interface bLength (%d)",
				 ifp->bLength);
			r = LIBUSB_ERROR_IO;
			goto err;
		}
		if (ifp->bLength > size) {
			usbi_warn(ctx, "short intf descriptor read %d/%d",
				 size, ifp->bLength);
			return parsed;
		}
		if (ifp->bNumEndpoints > USB_MAXENDPOINTS) {
			usbi_err(ctx, "too many endpoints (%d)", ifp->bNumEndpoints);
			r = LIBUSB_ERROR_IO;
			goto err;
		}

		usb_interface->num_altsetting++;
		ifp->extra = NULL;
		ifp->extra_length = 0;
		ifp->endpoint = NULL;

		if (interface_number == -1)
			interface_number = ifp->bInterfaceNumber;

		/* Skip over the interface */
		buffer += ifp->bLength;
		parsed += ifp->bLength;
		size -= ifp->bLength;

		begin = buffer;

		/* Skip over any interface, class or vendor descriptors */
		while (size >= DESC_HEADER_LENGTH) {
			usbi_parse_descriptor(buffer, "bb", &header, 0);
			if (header.bLength < DESC_HEADER_LENGTH) {
				usbi_err(ctx,
					 "invalid extra intf desc len (%d)",
					 header.bLength);
				r = LIBUSB_ERROR_IO;
				goto err;
			} else if (header.bLength > size) {
				usbi_warn(ctx,
					  "short extra intf desc read %d/%d",
					  size, header.bLength);
				return parsed;
			}

			/* If we find another "proper" descriptor then we're done */
			if ((header.bDescriptorType == LIBUSB_DT_INTERFACE) ||
					(header.bDescriptorType == LIBUSB_DT_ENDPOINT) ||
					(header.bDescriptorType == LIBUSB_DT_CONFIG) ||
					(header.bDescriptorType == LIBUSB_DT_DEVICE))
				break;

			buffer += header.bLength;
			parsed += header.bLength;
			size -= header.bLength;
		}

		/* Copy any unknown descriptors into a storage area for */
		/*  drivers to later parse */
		len = (int)(buffer - begin);
		if (len) {
			ifp->extra = malloc(len);
			if (!ifp->extra) {
				r = LIBUSB_ERROR_NO_MEM;
				goto err;
			}
			memcpy((unsigned char *) ifp->extra, begin, len);
			ifp->extra_length = len;
		}

		if (ifp->bNumEndpoints > 0) {
			struct libusb_endpoint_descriptor *endpoint;
			endpoint = calloc(ifp->bNumEndpoints, sizeof(struct libusb_endpoint_descriptor));
			ifp->endpoint = endpoint;
			if (!endpoint) {
				r = LIBUSB_ERROR_NO_MEM;
				goto err;
			}

			for (i = 0; i < ifp->bNumEndpoints; i++) {
				r = parse_endpoint(ctx, endpoint + i, buffer, size,
					host_endian);
				if (r < 0)
					goto err;
				if (r == 0) {
					ifp->bNumEndpoints = (uint8_t)i;
					break;
				}

				buffer += r;
				parsed += r;
				size -= r;
			}
		}

		/* We check to see if it's an alternate to this one */
		ifp = (struct libusb_interface_descriptor *) buffer;
		if (size < LIBUSB_DT_INTERFACE_SIZE ||
				ifp->bDescriptorType != LIBUSB_DT_INTERFACE ||
				ifp->bInterfaceNumber != interface_number)
			return parsed;
	}

	return parsed;
err:
	clear_interface(usb_interface);
	return r;
}

static void clear_configuration(struct libusb_config_descriptor *config)
{
	int i;
	if (config->interface) {
		for (i = 0; i < config->bNumInterfaces; i++)
			clear_interface((struct libusb_interface *)
					config->interface + i);
	}
	free((void *) config->interface);
	free((void *) config->extra);
}

static int parse_configuration(struct libusb_context *ctx,
	struct libusb_config_descriptor *config, unsigned char *buffer,
	int size, int host_endian)
{
	int i;
	int r;
	struct usb_descriptor_header header;
	struct libusb_interface *usb_interface;

	if (size < LIBUSB_DT_CONFIG_SIZE) {
		usbi_err(ctx, "short config descriptor read %d/%d",
			 size, LIBUSB_DT_CONFIG_SIZE);
		return LIBUSB_ERROR_IO;
	}

	usbi_parse_descriptor(buffer, "bbwbbbbb", config, host_endian);
	if (config->bDescriptorType != LIBUSB_DT_CONFIG) {
		usbi_err(ctx, "unexpected descriptor %x (expected %x)",
			 config->bDescriptorType, LIBUSB_DT_CONFIG);
		return LIBUSB_ERROR_IO;
	}
	if (config->bLength < LIBUSB_DT_CONFIG_SIZE) {
		usbi_err(ctx, "invalid config bLength (%d)", config->bLength);
		return LIBUSB_ERROR_IO;
	}
	if (config->bLength > size) {
		usbi_err(ctx, "short config descriptor read %d/%d",
			 size, config->bLength);
		return LIBUSB_ERROR_IO;
	}
	if (config->bNumInterfaces > USB_MAXINTERFACES) {
		usbi_err(ctx, "too many interfaces (%d)", config->bNumInterfaces);
		return LIBUSB_ERROR_IO;
	}

	usb_interface = calloc(config->bNumInterfaces, sizeof(struct libusb_interface));
	config->interface = usb_interface;
	if (!usb_interface)
		return LIBUSB_ERROR_NO_MEM;

	buffer += config->bLength;
	size -= config->bLength;

	config->extra = NULL;
	config->extra_length = 0;

	for (i = 0; i < config->bNumInterfaces; i++) {
		int len;
		unsigned char *begin;

		/* Skip over the rest of the Class Specific or Vendor */
		/*  Specific descriptors */
		begin = buffer;
		while (size >= DESC_HEADER_LENGTH) {
			usbi_parse_descriptor(buffer, "bb", &header, 0);

			if (header.bLength < DESC_HEADER_LENGTH) {
				usbi_err(ctx,
					 "invalid extra config desc len (%d)",
					 header.bLength);
				r = LIBUSB_ERROR_IO;
				goto err;
			} else if (header.bLength > size) {
				usbi_warn(ctx,
					  "short extra config desc read %d/%d",
					  size, header.bLength);
				config->bNumInterfaces = (uint8_t)i;
				return size;
			}

			/* If we find another "proper" descriptor then we're done */
			if ((header.bDescriptorType == LIBUSB_DT_ENDPOINT) ||
					(header.bDescriptorType == LIBUSB_DT_INTERFACE) ||
					(header.bDescriptorType == LIBUSB_DT_CONFIG) ||
					(header.bDescriptorType == LIBUSB_DT_DEVICE))
				break;

			usbi_dbg("skipping descriptor 0x%x", header.bDescriptorType);
			buffer += header.bLength;
			size -= header.bLength;
		}

		/* Copy any unknown descriptors into a storage area for */
		/*  drivers to later parse */
		len = (int)(buffer - begin);
		if (len) {
			/* FIXME: We should realloc and append here */
			if (!config->extra_length) {
				config->extra = malloc(len);
				if (!config->extra) {
					r = LIBUSB_ERROR_NO_MEM;
					goto err;
				}

				memcpy((unsigned char *) config->extra, begin, len);
				config->extra_length = len;
			}
		}

		r = parse_interface(ctx, usb_interface + i, buffer, size, host_endian);
		if (r < 0)
			goto err;
		if (r == 0) {
			config->bNumInterfaces = (uint8_t)i;
			break;
		}

		buffer += r;
		size -= r;
	}

	return size;

err:
	clear_configuration(config);
	return r;
}

static int raw_desc_to_config(struct libusb_context *ctx,
	unsigned char *buf, int size, int host_endian,
	struct libusb_config_descriptor **config)
{
	struct libusb_config_descriptor *_config = malloc(sizeof(*_config));
	int r;
	
	if (!_config)
		return LIBUSB_ERROR_NO_MEM;

	r = parse_configuration(ctx, _config, buf, size, host_endian);
	if (r < 0) {
		usbi_err(ctx, "parse_configuration failed with error %d", r);
		free(_config);
		return r;
	} else if (r > 0) {
		usbi_warn(ctx, "still %d bytes of descriptor data left", r);
	}
	
	*config = _config;
	return LIBUSB_SUCCESS;
}

int libusb_get_active_config_descriptor(libusb_device *dev, struct libusb_config_descriptor **config)
{
	int config_val, err = LIBUSB_SUCCESS;
	struct libusb_config_descriptor _config;
	unsigned char *buff = NULL, tmp[LIBUSB_DT_CONFIG_SIZE];
	int host_endian = 0;

	*config = NULL;
	err = libusb_get_configuration((libusb_device_handle *)dev, &config_val);
	if (err != LIBUSB_SUCCESS) return err;

	err = libusb_get_descriptor((libusb_device_handle *)dev, USB_DT_CONFIG,
			config_val - 1, tmp, LIBUSB_DT_CONFIG_SIZE);
	if (err < 0) return err;

	usbi_parse_descriptor(tmp, "bbw", &_config, host_endian);
	buff = malloc(_config.wTotalLength);
	if (!buff) return LIBUSB_ERROR_NO_MEM;

	err = libusb_get_descriptor((libusb_device_handle *)dev, USB_DT_CONFIG,
			config_val - 1, buff, _config.wTotalLength);
	if (err >= 0)
	{
		vsflinux_libusb.curdev = (struct vsfusbh_libusb_dev_t *)dev;
		memset(&vsflinux_libusb.curdev->dev->ep_mps_in[1], 0,
				sizeof(vsflinux_libusb.curdev->dev->ep_mps_in) - 2);
		memset(&vsflinux_libusb.curdev->dev->ep_mps_out[1], 0,
				sizeof(vsflinux_libusb.curdev->dev->ep_mps_out) - 2);
		err = raw_desc_to_config(NULL, buff, err, host_endian, config);
	}

	free(buff);
	return err;
}

void libusb_free_config_descriptor(struct libusb_config_descriptor *config)
{
	if (!config)
		return;

	clear_configuration(config);
	free(config);
}

int libusb_release_interface(libusb_device_handle *dev_handle, int interface_number)
{
	return LIBUSB_SUCCESS;
}

int libusb_claim_interface(libusb_device_handle *dev_handle, int interface_number)
{
	return LIBUSB_SUCCESS;
}

struct libusb_transfer *libusb_alloc_transfer(int iso_packets)
{
	return (struct libusb_transfer *)calloc(sizeof(struct libusb_transfer_ext), 1);
}

static struct vsfsm_state_t *
libusb_transfer_evthandler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
	struct libusb_transfer_ext *transfer_ext = (struct libusb_transfer_ext *)sm->user_data;
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)transfer_ext->transfer.dev_handle;
	struct vsfhcd_urb_t *urb = transfer_ext->urb;

	switch (evt)
	{
	case VSFSM_EVT_URB_COMPLETE:
		transfer_ext->transfer.actual_length = urb->actual_length;
		transfer_ext->transfer.status =
			urb->status == URB_OK ? LIBUSB_TRANSFER_COMPLETED : LIBUSB_TRANSFER_ERROR;
		vsfusbh_free_urb(ldev->usbh, &transfer_ext->urb);
		vsflist_append(&vsflinux_libusb.translist, &transfer_ext->list);
		vsfsm_thread_sendevt(vsflinux_libusb.thread, VSFSM_EVT_URB_COMPLETE);
		break;
	}
	return NULL;
}

int libusb_submit_transfer(struct libusb_transfer *transfer)
{
	struct libusb_transfer_ext *transfer_ext = (struct libusb_transfer_ext *)transfer;
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)transfer->dev_handle;
	struct vsfhcd_device_t *hcddev = &ldev->dev->hcddev;
	unsigned char endpoint = transfer->endpoint;
	unsigned char epaddr = endpoint & 0x7F;
	int err = LIBUSB_SUCCESS;
	struct vsfhcd_urb_t *urb;
	uint8_t origlevel;

	if (!transfer_ext->urb)
		transfer_ext->urb = vsfusbh_alloc_urb(ldev->usbh);
	urb = transfer_ext->urb;
	if (!urb) return -1;
	urb->hcddev = &ldev->dev->hcddev;

	urb->transfer_buffer = transfer->buffer;
	urb->transfer_length = transfer->length;
	urb->timeout = transfer->timeout;

	switch (transfer->type)
	{
	case LIBUSB_TRANSFER_TYPE_CONTROL:
		urb->setup_packet = *(struct usb_ctrlrequest_t *)transfer->buffer;
		urb->pipe = (urb->setup_packet.bRequestType & USB_DIR_MASK) == USB_DIR_IN ?
			usb_rcvctrlpipe(hcddev, epaddr) : usb_sndctrlpipe(hcddev, epaddr);
		urb->transfer_buffer = (uint8_t *)urb->transfer_buffer + sizeof(struct usb_ctrlrequest_t);
		urb->transfer_length -= sizeof(struct usb_ctrlrequest_t);
		break;
	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
		urb->pipe = (endpoint & USB_DIR_MASK) == USB_DIR_IN ?
			usb_rcvisocpipe(hcddev, epaddr) : usb_sndisocpipe(hcddev, epaddr);
		break;
	case LIBUSB_TRANSFER_TYPE_BULK:
		urb->pipe = (endpoint & USB_DIR_MASK) == USB_DIR_IN ?
			usb_rcvbulkpipe(hcddev, epaddr) : usb_sndbulkpipe(hcddev, epaddr);
		break;
	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
		urb->pipe = (endpoint & USB_DIR_MASK) == USB_DIR_IN ?
			usb_rcvintpipe(hcddev, epaddr) : usb_sndintpipe(hcddev, epaddr);
		break;
	}

	transfer_ext->sm.init_state.evt_handler = libusb_transfer_evthandler;
	transfer_ext->sm.user_data = transfer;
	vsfsm_init(&transfer_ext->sm);
	urb->notifier_sm = &transfer_ext->sm;

	origlevel = vsfsm_sched_lock();
	if (vsfusbh_submit_urb(ldev->usbh, urb))
		err = LIBUSB_ERROR_IO;
	vsfsm_sched_unlock(origlevel);
	return err;
}

int libusb_cancel_transfer(struct libusb_transfer *transfer)
{
	struct libusb_transfer_ext *transfer_ext = (struct libusb_transfer_ext *)transfer;
	struct vsfusbh_libusb_dev_t *ldev = (struct vsfusbh_libusb_dev_t *)transfer->dev_handle;
	uint8_t origlevel = vsfsm_sched_lock();

	transfer_ext->urb->transfer_flags |= URB_BUFFER_DYNALLOC;
	vsfusbh_free_urb(ldev->usbh, &transfer_ext->urb);
	transfer_ext->urb = vsfusbh_alloc_urb(ldev->usbh);
	vsfsm_sched_unlock(origlevel);
	if (!transfer_ext->urb)
		return LIBUSB_ERROR_NO_MEM;
	return LIBUSB_SUCCESS;
}

void libusb_free_transfer(struct libusb_transfer *transfer)
{
	if (transfer != NULL)
		free(transfer);
}

int libusb_handle_events_timeout_completed(libusb_context *ctx,
	struct timeval *tv, int *completed)
{
	struct vsflinux_fd_t *sfd;
	for (int i = 0; i < dimof(vsflinux_libusb.pollfd); i++)
	{
		sfd = vsflinux_fd_get(vsflinux_libusb.pollfd[i].fd);
		sfd->rxevt = false;
		sfd->txpend = sfd->rxpend = -1;
	}
	return LIBUSB_SUCCESS;
}

int libusb_handle_events_completed(libusb_context *ctx, int *completed)
{
	struct timeval tv;
	tv.tv_sec = 60;
	tv.tv_usec = 0;
	return libusb_handle_events_timeout_completed(ctx, &tv, completed);
}

int libusb_handle_events_timeout(libusb_context *ctx, struct timeval *tv)
{
	return libusb_handle_events_timeout_completed(ctx, tv, NULL);
}

int libusb_handle_events(libusb_context *ctx)
{
	struct timeval tv;
	tv.tv_sec = 60;
	tv.tv_usec = 0;
	return libusb_handle_events_timeout_completed(ctx, &tv, NULL);
}

const struct libusb_pollfd** libusb_get_pollfds(libusb_context *ctx)
{
	struct libusb_pollfd **pollfd = (struct libusb_pollfd **)
		calloc(dimof(vsflinux_libusb.pollfd) + 1, sizeof(struct libusb_pollfd *));

	if (pollfd != NULL)
	{
		int i;
		for (i = 0; i < dimof(vsflinux_libusb.pollfd); i++)
			pollfd[i] = &vsflinux_libusb.pollfd[i];
		pollfd[i] = NULL;
	}
	return (const struct libusb_pollfd **)pollfd;
}

void libusb_free_pollfds(const struct libusb_pollfd **pollfds)
{
	if (pollfds != NULL)
		free(pollfds);
}
